# coding:utf-8
'''
	建模工具模块
	汉中库得网络科技
	电子信箱：yyc1030@qq.com
'''

from PySide2 import QtGui
from PySide2 import QtWidgets
from PySide2 import QtCore
from pymxs import runtime as rt
import pymxs

'''
	建模工具Action
'''
class BuildModelAction(QtWidgets.QAction):
	
	def __init__(self, app):
		self.app = app
		icon = QtGui.QIcon(app.rootPath + "/icon/build_model.png")
		super(BuildModelAction, self).__init__(icon, "建模工具", app.mainWindow)
		self.setCheckable(True)
		self.triggered.connect(self.toggleBuildModelWindow)
		self.buildModelWindow = None
		
	# 打开或关闭建模工具窗口
	def toggleBuildModelWindow(self):
		if self.isChecked():
			if not self.buildModelWindow:
				self.buildModelWindow = BuildModelWindow(self.app)
			self.buildModelWindow.show()
		else:
			self.buildModelWindow.close()
		
'''
	建模工具窗口
'''
class BuildModelWindow(QtWidgets.QMainWindow):
	
	def __init__(self, app):
		super(BuildModelWindow, self).__init__(app.mainWindow)
		self.app = app
		self.setWindowTitle("建模工具")
		self.setWindowIcon(app.logo)

		center = QtWidgets.QWidget()
		self.content = QtWidgets.QVBoxLayout()
		self.content.setMargin(5)
		self.content.setSpacing(5)
		
		center.setLayout(self.content)
		self.setCentralWidget(center)
		
		self.__initOKWall()
		self.__initOKDoorHole()
		self.__initOKWindowHole()
		
		self.content.addStretch()
		
		# 设置窗口大小
		self.width = 250
		self.height = 350
		self.setMaximumSize(self.width, self.height)
		self.setMinimumSize(self.width, self.height)
		# self.setGeometry(35, 155, self.width, self.height)
		self.resize(self.width, self.height)
		
	# 初始化一键墙体UI
	def __initOKWall(self):
		widget = QtWidgets.QGroupBox("一键墙体")
		hbox = QtWidgets.QHBoxLayout()
		hbox.setMargin(5)
		hbox.setSpacing(5)
		widget.setLayout(hbox)
		label = QtWidgets.QLabel("层高:")
		label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
		hbox.addWidget(label)
		self.floorHeight = QtWidgets.QSpinBox()
		self.floorHeight.setRange(1, 10000)
		self.floorHeight.setValue(2800)
		hbox.addWidget(self.floorHeight)
		btn = QtWidgets.QPushButton("一键墙体")
		btn.clicked.connect(self.onOKWallClicked)
		hbox.addWidget(btn)
		self.content.addWidget(widget)
		
	# 初始化一键门洞UI
	def __initOKDoorHole(self):
		widget = QtWidgets.QGroupBox("一键门洞")
		hbox = QtWidgets.QHBoxLayout()
		hbox.setMargin(5)
		hbox.setSpacing(0)
		widget.setLayout(hbox)
		label = QtWidgets.QLabel("门高:")
		label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
		hbox.addWidget(label)
		self.doorHoleHeight = QtWidgets.QSpinBox()
		self.doorHoleHeight.setRange(1, 10000)
		self.doorHoleHeight.setValue(2100)
		hbox.addWidget(self.doorHoleHeight)
		btn = QtWidgets.QPushButton("开门洞")
		btn.clicked.connect(self.createDoorHole)
		hbox.addWidget(btn)
		dbBtn = QtWidgets.QPushButton("桥接门洞")
		dbBtn.clicked.connect(self.bridgeDoorHole)
		hbox.addWidget(dbBtn)
		self.content.addWidget(widget)
		
	# 初始化一键窗洞UI
	def __initOKWindowHole(self):
		widget =  QtWidgets.QGroupBox("一键窗洞")
		grid = QtWidgets.QGridLayout()
		grid.setMargin(5)
		grid.setSpacing(5)
		widget.setLayout(grid)
		
		grid.addWidget(QtWidgets.QLabel("窗高:"))
		
		self.windowHeight =  QtWidgets.QSpinBox()
		self.windowHeight.setRange(1, 10000)
		self.windowHeight.setValue(1500)
		grid.addWidget(self.windowHeight, 0, 1)
		
		grid.addWidget(QtWidgets.QLabel("距地:"), 0, 2)
		
		self.windowBottom = QtWidgets.QSpinBox()
		self.windowBottom.setRange(1, 100000)
		self.windowBottom.setValue(900)
		grid.addWidget(self.windowBottom, 0, 3)
		
		self.isExtrude = QtWidgets.QCheckBox("挤出窗台")
		self.isExtrude.setChecked(True)
		grid.addWidget(self.isExtrude, 1, 0, 1, 2)
		
		btn = QtWidgets.QPushButton("开窗洞")
		btn.clicked.connect(self.createWindowHole)
		grid.addWidget(btn, 2, 0, 1, 2)
		
		dbBtn = QtWidgets.QPushButton("桥接窗洞")
		dbBtn.clicked.connect(self.bridgeWindowHole)
		grid.addWidget(dbBtn, 2, 2, 1, 2)
		
		self.content.addWidget(widget)
		
	# 当“一键墙体”单击时
	def onOKWallClicked(self):
		with pymxs.undo(True, "一键墙体"):
			if len(rt.selection) == 1 and rt.isShapeObject(rt.selection[0]):
				obj = rt.selection[0]
				rt.addModifier(obj, rt.Extrude(amount=self.floorHeight.value()))
				rt.addModifier(obj, rt.NormalModifier(flip=True))
				rt.convertToPoly(obj)
				obj.ignoreBackfacing = True
				rt.redrawViews()
		
	# 开门洞
	def createDoorHole(self):
		with pymxs.undo(True, "开门洞"):
			if len(rt.selection) == 1 and rt.superClassOf(rt.selection[0]) == rt.GeometryClass:
				faces = list(rt.toArray(rt.polyOp.getFaceSelection(rt.selection[0])))
				poly = rt.selection[0]
				for face in faces:
					rt.setFaceSelection(poly, [face])
					# 获取面的最下沿的Z值
					minZ = util.getMinZFromFace(poly, face)
					# 计算门洞实际高度
					doorHoleHeight = minZ + self.doorHoleHeight.value()
					# 获取选中面的垂直边
					edges = util.getVerticalEdges(poly, face)
					# 切换到边层级
					rt.subObjectLevel = 2
					rt.polyop.setEdgeSelection(poly, edges)
					poly.connectEdgeSegments = 1
					poly.EditablePoly.ConnectEdges()
					# 获取连接边
					edges = list(rt.toArray(rt.polyop.getEdgeSelection(poly)))
					# 计算边的将移动的高度
					verts = list(rt.toArray(rt.polyop.getVertsUsingEdge(poly, edges)))
					v = rt.polyop.getVert(poly, verts[0])
					moveZ = doorHoleHeight - v.z
					rt.move(poly.selectedEdges, rt.point3(0, 0, moveZ))
					# 切换回面层级
					rt.subObjectLevel = 4
					rt.setFaceSelection(poly, [face])
					# 挤出门洞
					rt.polyop.extrudeFaces(poly, [face], -240)
				rt.redrawViews()
		
	# 桥接门洞
	def bridgeDoorHole(self):
		with pymxs.undo(True, "桥接门洞"):
			if len(rt.selection) == 1 and rt.superClassOf(rt.selection[0]) == rt.GeometryClass:
				faces = list(rt.toArray(rt.polyop.getFaceSelection(rt.selection[0])))
				if len(faces) == 2:
					poly = rt.selection[0]
					# 用于记录门洞两段顶上的面的ID
					topFaces = []
					# 分别将两个面分割出门洞的高度
					for face in faces:
						rt.setFaceSelection(poly, [face])
						# 获取面的最下沿的Z值
						minZ = util.getMinZFromFace(poly, face)
						# 计算门洞实际高度
						doorHoleHeight = minZ + self.doorHoleHeight.value()
						# 获取选中面的垂直边
						edges = util.getVerticalEdges(poly, face)
						# 切换到边层级
						rt.subObjectLevel = 2
						rt.polyop.setEdgeSelection(poly, edges)
						poly.connectEdgeSegments = 1
						poly.EditablePoly.ConnectEdges()
						# 获取连接边
						edges = list(rt.toArray(rt.polyop.getEdgeSelection(poly)))
						# 计算边的将移动的高度
						verts = list(rt.toArray(rt.polyop.getVertsUsingEdge(poly, edges)))
						v = rt.polyop.getVert(poly, verts[0])
						moveZ = doorHoleHeight - v.z
						rt.move(poly.selectedEdges, rt.point3(0, 0, moveZ))
						# 切换回面层级
						rt.subObjectLevel = 4
						# 求出当前选中面的最上面的面
						topFaces.append(util.getTopFace(poly, list(rt.toArray(rt.polyop.getFaceSelection(rt.selection[0])))))
					# 选中门洞顶上的两个面并桥接
					rt.setFaceSelection(poly, topFaces)
					poly.bridgeSelected = 1
					poly.EditablePoly.Bridge()
					# 将最顶上的面删除
					top = util.getTopFace(poly, list(rt.toArray(rt.polyop.getFaceSelection(rt.selection[0]))))
					rt.setFaceSelection(poly, [top])
					poly.EditablePoly.delete(rt.name("Face"))
					rt.redrawViews()
				else:
					box = QtWidgets.QMessageBox(self.app.mainWindow)
					box.setWindowTitle("操作提示")
					box.setWindowIcon(self.app.logo)
					box.setText('''
							<p>请选择门洞两侧的面进行该操作！</p>
						''')
					box.exec_()
					
	# 开窗洞
	def createWindowHole(self):
		with pymxs.undo(True, "开窗洞"):
			if len(rt.selection) == 1 and rt.superClassOf(rt.selection[0]) == rt.GeometryClass:
				faces = list(rt.toArray(rt.polyop.getFaceSelection(rt.selection[0])))
				poly = rt.selection[0]
				# 获取地面高度
				ground = util.getMinZFromFace(poly, faces[0])
				# 求出选中面的垂直边
				edges = util.getVEdges(poly, faces)
				# 切换到边层级
				rt.subObjectLevel = 2
				# 连接出窗台线和窗顶线
				rt.polyop.setEdgeSelection(poly, edges)
				poly.connectEdgeSegments = 2
				poly.EditablePoly.ConnectEdges()
				# 获取窗台线和窗顶线
				selectEdges = list(rt.toArray(rt.polyop.getEdgeSelection(poly)))
				windowBottomEdges = util.getBottomEdges(poly, selectEdges)
				# 移除窗台线，剩下的就是窗顶线
				for edge in windowBottomEdges:
					selectEdges.remove(edge)
				# 计算窗台线移动的高度并移动
				rt.polyop.setEdgeSelection(poly, windowBottomEdges)
				verts = list(rt.toArray(rt.polyop.getVertsUsingEdge(poly, windowBottomEdges)))
				v = rt.polyop.getVert(poly, verts[0])
				windowBottomHeight = ground + self.windowBottom.value()
				moveZ = windowBottomHeight - v.z
				rt.move(poly.selectedEdges, rt.point3(0, 0, moveZ))
				# 计算窗顶线移动的高度并移动
				rt.polyop.setEdgeSelection(poly, selectEdges)
				verts = list(rt.toArray(rt.polyop.getVertsUsingEdge(poly, selectEdges)))
				v = rt.polyop.getVert(poly, verts[0])
				windowTopHeight = ground + self.windowBottom.value() + self.windowHeight.value()
				moveZ = windowTopHeight - v.z
				rt.move(poly.selectedEdges, rt.point3(0, 0, moveZ))
				# 切换到面层级，此时选中的是所有的面，即上中下的面全部被选中
				rt.subObjectLevel = 4
				faces = list(rt.toArray(rt.polyop.getFaceSelection(poly)))
				topFaces = util.getTopFaces(poly, faces)
				# 移除最顶上的两个面
				for face in topFaces:
					faces.remove(face)
				# 再从剩下的面中找出最顶上的一组面，这组面便是窗洞面
				holeFaces = util.getTopFaces(poly, faces)
				rt.polyop.setFaceSelection(poly, holeFaces)
				# 判断是否需要挤出
				if self.isExtrude.isChecked():
					poly.EXTRUSIONTYPE = 1
					poly.faceExtrudeHeight = -150
					poly.EditablePoly.buttonOp(rt.name("Extrude"))
				rt.redrawViews()
				
				
	# 桥接窗洞
	def bridgeWindowHole(self):
		with pymxs.undo(True, "桥接窗洞"):
			if len(rt.selection) == 1 and rt.superClassOf(rt.selection[0]) == rt.GeometryClass:
				faces = list(rt.toArray(rt.polyop.getFaceSelection(rt.selection[0])))
				if len(faces) == 2:
					poly = rt.selection[0]
					topFaces = []
					bottomFaces = []
					# 获取地面高度
					ground = util.getMinZFromFace(poly, faces[0])
					for face in faces:
						rt.polyop.setFaceSelection(poly, [face])
						# 切换到边层级，连接出窗户上下沿的线
						rt.subObjectLevel = 2
						edges = util.getVerticalEdges(poly, face)
						rt.polyop.setEdgeSelection(poly, edges)
						# 连接两条线
						rt.polyop.setEdgeSelection(poly, edges)
						poly.connectEdgeSegments = 2
						poly.EditablePoly.ConnectEdges()
						# 获取窗台线和窗顶线
						topEdge = list(rt.toArray(rt.polyop.getEdgeSelection(poly)))
						bottomEdge = util.getBottomEdges(poly, topEdge)
						topEdge.remove(bottomEdge[0])
						# 计算窗台线移动的高度并移动
						rt.polyop.setEdgeSelection(poly, bottomEdge)
						verts = list(rt.toArray(rt.polyop.getVertsUsingEdge(poly, bottomEdge)))
						v = rt.polyop.getVert(poly, verts[0])
						bottomHeight = ground + self.windowBottom.value()
						moveZ = bottomHeight - v.z
						rt.move(poly.selectedEdges, rt.point3(0, 0, moveZ))
						# 计算窗顶线移动的高度并移动
						rt.polyop.setEdgeSelection(poly, topEdge)
						verts = list(rt.toArray(rt.polyop.getVertsUsingEdge(poly, topEdge)))
						v = rt.polyop.getVert(poly, verts[0])
						topHeight = ground + self.windowBottom.value() + self.windowHeight.value()
						moveZ = topHeight - v.z
						rt.move(poly.selectedEdges, rt.point3(0, 0, moveZ))
						# 切换到面层级，获取顶上的面和底部的面，供下一步桥接用
						rt.subObjectLevel = 4
						tempFaces = list(rt.toArray(rt.polyop.getFaceSelection(poly)))
						top = util.getTopFace(poly, tempFaces)
						tempFaces.remove(top)
						middle = util.getTopFace(poly, tempFaces)
						tempFaces.remove(middle)
						topFaces.append(top)
						bottomFaces.append(tempFaces[0])
					# 桥接窗洞顶部和底部
					rt.polyop.setFaceSelection(poly, topFaces + bottomFaces)
					poly.bridgeSelected = 1
					poly.EditablePoly.Bridge()
					# 移除最顶上的面和最底下的面
					selectFaces = list(rt.toArray(rt.polyop.getFaceSelection(rt.selection[0])))
					top = util.getTopFace(poly, selectFaces)
					bottom = util.getBottomFace(poly, selectFaces)
					rt.setFaceSelection(poly, [top, bottom])
					poly.EditablePoly.delete(rt.name("Face"))
					rt.redrawViews()
		
	# 监听关闭事件
	def closeEvent(self, event):
		self.app.toolbar.buildModelAction.setChecked(False)
		
'''
	建模工具集
'''
class util():
	
	# 获取一个面的垂直边
	@staticmethod
	def getVerticalEdges(poly, face):
		# 获取组成面的四条边
		edges = list(rt.toArray(rt.polyop.getFaceEdges(poly, face)))
		# 获取组成面的四个点
		verts = list(rt.toArray(rt.polyop.getVertsUsingFace(poly, face)))
		# 分别取得顶端的两个点和底端的两个点
		topVerts = util.getTopVerts(poly, verts)
		bottomVerts = util.getBottomVerts(poly, verts)
		# 从组成面的四个边中，删除顶边和底边
		edges.remove(util.getEdgeFrom2Verts(poly, topVerts))
		edges.remove(util.getEdgeFrom2Verts(poly, bottomVerts))
		return edges
		
	'''
		获取多个面的垂直边
		@param poly 可编辑多边形
		@param faces 面ID列表
	'''
	@staticmethod
	def getVEdges(poly, faces):
		edges = []
		for face in faces:
			temp = util.getVerticalEdges(poly, face)
			for edge in temp:
				edges.append(edge)
		return list(set(edges))
		
	# 获取一个面的最下沿的Z轴值
	@staticmethod
	def getMinZFromFace(poly, face):
		# 获取组成面的四条边
		edges = list(rt.toArray(rt.polyop.getFaceEdges(poly, face)))
		# 获取组成面的四个点
		verts = list(rt.toArray(rt.polyop.getVertsUsingFace(poly, face)))
		# 取得底端的两个点
		verts = util.getBottomVerts(poly, verts)
		v = rt.polyop.getVert(poly, verts[0])
		return v.z

	'''
		获取几个点当中最顶端的点的列表
		@param: poly 可编辑多边形
		@param: verts N个点的列表
	'''
	@staticmethod
	def getTopVerts(poly, verts):
		maxZ = -9999
		# 获取最顶端的点的Z轴坐标值
		for vert in verts:
			v = rt.polyop.getVert(poly, vert)
			if v.z > maxZ:
				maxZ = v.z
		list = []
		for vert in verts:
			v = rt.polyop.getVert(poly, vert)
			if v.z == maxZ:
				list.append(vert)
		return list
		
	'''
		获取几个点当中最底下的点的列表
		@param poly 可编辑多边形
		@param verts N个点的列表
	'''
	@staticmethod
	def getBottomVerts(poly, verts):
		minZ = 9999
		# 获取最底下的点的Z轴坐标值
		for vert in verts:
			v = rt.polyop.getVert(poly, vert)
			if v.z < minZ:
				minZ = v.z
		list = []
		for vert in verts:
			v = rt.polyop.getVert(poly, vert)
			if v.z == minZ:
				list.append(vert)
		return list
		
	'''
		根据两个点获取一条边
		@param poly 可编辑多边形
		@param verts 两个点的列表
	'''
	@staticmethod
	def getEdgeFrom2Verts(poly, verts):
		edges1 = list(rt.toArray(rt.polyop.getEdgesUsingVert(poly, verts[0])))
		edges2 = list(rt.toArray(rt.polyop.getEdgesUsingVert(poly, verts[1])))
		return list(set(edges1).intersection(set(edges2)))[0]
		
	'''
		获取一组边中，最底端的边的ID列表
		@param poly 可编辑多边形
		@param edges 边ID列表
	'''
	@staticmethod
	def getBottomEdges(poly, edges):
		verts = list(rt.toArray(rt.polyop.getVertsUsingEdge(poly, edges)))
		# 获取最下面的点的Z轴值
		verts = util.getBottomVerts(poly, verts)
		v = rt.polyop.getVert(poly, verts[0])
		# 找出Z轴值相同的边
		result = []
		for edge in edges:
			verts = list(rt.toArray(rt.polyop.getVertsUsingEdge(poly, [edge])))
			temp = rt.polyop.getVert(poly, verts[0])
			if temp.z == v.z:
				result.append(edge)
		return result
		
	'''
		求出所给的一组面中最顶上的一个面的ID
		@param poly 可编辑多边形
		@param faces 面ID列表
	'''
	@staticmethod
	def getTopFace(poly, faces):
		topCenter = rt.polyop.getFaceCenter(poly, faces[0])
		top = faces[0]
		for face in faces:
			temp = rt.polyop.getFaceCenter(poly, face)
			if temp.z > topCenter.z:
				top = face
				topCenter = temp
		return top
		
	'''
		求出所给的一组面中最顶上的一组面的ID
		@param poly 可编辑多边形
		@param faces 面ID列表
	'''
	@staticmethod
	def getTopFaces(poly, faces):
		# 假设第一个面为最顶上的面
		topCenter = rt.polyop.getFaceCenter(poly, faces[0])
		# 求出最顶上的面的中心位置
		for face in faces:
			temp = rt.polyop.getFaceCenter(poly, face)
			if temp.z > topCenter.z:
				topCenter = temp
		result = []
		for face in faces:
			temp = rt.polyop.getFaceCenter(poly, face)
			if temp.z == topCenter.z:
				result.append(face)
		return result
		
	'''
		求出所给的一组面中最底下的一个面的ID
		@param poly 可编辑多边形
		@param faces 面ID列表
	'''
	@staticmethod
	def getBottomFace(poly, faces):
		bottomCenter = rt.polyop.getFaceCenter(poly, faces[0])
		bottom = faces[0]
		for face in faces:
			temp = rt.polyop.getFaceCenter(poly, face)
			if temp.z < bottomCenter.z:
				bottom = face
				bottomCenter = temp
		return bottom